#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>

#define LED_ON 1
#define LED_OFF 0

#define GPIOLED_CNT 1           // 设备数量
#define GPIOLED_NAME "gpio_led" // 设备名称

// 设备结构体
struct gpioled_dev
{
    dev_t devid;            // 设备号
    int major;              // 主设备号
    int minor;              // 次设备号
    struct cdev cdev;       // 字符设备
    struct class *class;    // 设备类指针
    struct device *device;  // 设备指针
    struct device_node *nd; // 设备节点指针
    int led_gpio;           // 得到的gpio序号
};

struct gpioled_dev gpioled;

// led等操作函数
void led_control(unsigned int cmd)
{
    if (cmd == LED_ON)
    {
        gpio_set_value(gpioled.led_gpio, 0);
    }
    else if (cmd == LED_OFF)
    {
        gpio_set_value(gpioled.led_gpio, 1);
    }
}

ssize_t led_write(struct file *filp, const char __user *buff, size_t size, loff_t *pos)
{
    int ret;
    unsigned int cmd;
    // 获取用户空间数据
    ret = copy_from_user(&cmd, buff, size);
    if (ret > 0)
    {
        printk("fail copt_from_user\r\n");
        return -1;
    }

    led_control(cmd);
    return 0;
}

int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &gpioled;
    return 0;
}

int led_close(struct inode *inode, struct file *filp)
{
    struct gpioled_dev *data = filp->private_data;
    printk("major = %d, minor = %d\r\n", data->major, data->minor);
    return 0;
}

// 字符设备操作集
struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .write = led_write,
    .open = led_open,
    .release = led_close,
};

static int __init gpio_led_init(void)
{
    int ret;
    // 注册设备号
    if (gpioled.major) // 如果有主设备号
    {
        gpioled.devid = MKDEV(gpioled.major, 0);
        ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
    }
    else // 没有主设备号
    {
        ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
        gpioled.major = MAJOR(gpioled.devid);
        gpioled.minor = MINOR(gpioled.devid);
    }
    if (ret)
    {
        printk("Failed to register!\r\n");
        ret = -EINVAL;
        goto fail_register;
    }

    // 创建cdev
    cdev_init(&gpioled.cdev, &led_fops);
    // 添加到内核
    ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
    if (ret)
    {
        printk("Failed to cdev_add!\r\n");
        ret = -EINVAL;
        goto fail_cdev_add;
    }

    // 创建设备类
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if (IS_ERR(gpioled.class))
    {
        ret = PTR_ERR(gpioled.class);
        printk("Failed to create class!\r\n");
        goto fail_class_create;
    }

    // 创建设备节点
    gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
    if (IS_ERR(gpioled.device))
    {
        ret = PTR_ERR(gpioled.device);
        printk("Failed to create device!\r\n");
        goto fail_device_create;
    }

    // 获取设备树属性
    gpioled.nd = of_find_node_by_path("/gpioled");
    if (!gpioled.nd)
    {
        ret = -EINVAL;
        pr_err("Failed to find node\r\n");
        goto fail_find_gpio;
    }

    // 根据名字得到led_gpios的内容
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led_gpios", 0);
    if (gpioled.led_gpio < 0)
    {
        ret = -EINVAL;
        printk("Failed to find gpio\r\n");
        goto fail_find_gpio;
    }

    // 根据gpio序号请求gpio
    ret = gpio_request(gpioled.led_gpio, "led_gpios");
    if (ret)
    {
        ret = -EINVAL;
        printk("Failed to gpio request\r\n");
        goto fail_find_gpio;
    }

    // 设置gpio为输出
    ret = gpio_direction_output(gpioled.led_gpio, 1);
    if (ret)
    {
        ret = -EINVAL;
        printk("Failed to set output\r\n");
        goto fail_find_gpio;
    }

    // 输出低电平点亮
    gpio_set_value(gpioled.led_gpio, 0);

    return 0;
fail_find_gpio:
    device_destroy(gpioled.class, gpioled.devid);
fail_device_create:
    class_destroy(gpioled.class);
fail_class_create:
    cdev_del(&gpioled.cdev);
fail_cdev_add:
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
fail_register:
    return ret;
}

static void __exit gpio_led_exit(void)
{
    // 输出高电平熄灭
    gpio_set_value(gpioled.led_gpio, 1);
    // 卸载请求到的gpio
    gpio_free(gpioled.led_gpio);
    // 卸载设备
    device_destroy(gpioled.class, gpioled.devid);
    // 注销设备类
    class_destroy(gpioled.class);
    // 卸载cdev
    cdev_del(&gpioled.cdev);
    // 注销设备号
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
}

module_init(gpio_led_init);
module_exit(gpio_led_exit);
MODULE_LICENSE("GPL");